﻿namespace EasyToolkit.Excel.BindingTemplates.Renderer
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using EasyToolkit.BindingTemplates.Context;
    using EasyToolkit.BindingTemplates.Definitions;
    using EasyToolkit.Excel.BindingTemplates.Controls;
    using EasyToolkit.Excel.BindingTemplates.Definitions;
    using Microsoft.Office.Interop.Excel;

    class ExcelPartRenderer : IDisposable
    {
        #region attributes and properties
        protected ExcelTemplateDefinitionPart templateDefinition;
        protected IBindingContext bindingContext;

        protected Range firstRangeTo;
        protected Range elementFirstRangeTo;

        protected Range currentRenderingFrom;
        protected Range currentRenderingTo;

        public List<List<IBindingContextItem>> DataRows
        { get; private set; }

        public int Height
        { get; private set; }

        public int Width
        { get; private set; }

        public Range OutputRange
        { get; private set; }
        #endregion

        #region .ctors and factories
        public ExcelPartRenderer(ExcelTemplateView view)
        {
            //this.view = view;
            templateDefinition = (ExcelTemplateDefinitionPart) view.TemplateDefinition;
            bindingContext = view.BindingContext;

            currentRenderingFrom = templateDefinition.TemplateFirstCell;
            firstRangeTo = elementFirstRangeTo = currentRenderingTo = view.FirstOutputCell;

            Init();
        }

        private ExcelPartRenderer(ExcelTemplateDefinitionPart templateDefinition, IBindingContext bindingContext, ExcelPartRenderer host)
        {
            this.templateDefinition = templateDefinition;
            this.bindingContext = bindingContext;

            currentRenderingFrom = templateDefinition.TemplateFirstCell;
            firstRangeTo = elementFirstRangeTo = currentRenderingTo = host.currentRenderingTo;
            
            Init();
        }
        #endregion

        #region public methods
        virtual public void Render()
        {
            Worksheet worksheetTo = currentRenderingTo.Worksheet;

            if (bindingContext.Header != null)
            {
                ExcelPartRenderer header = new ExcelPartRenderer((ExcelTemplateDefinitionPart)templateDefinition.Header, bindingContext.Header, this);
                header.Render();
                DataRows.AddRange(header.DataRows);
                Height = header.Height;
                Width = header.Width;
                if (templateDefinition.Orientation == Orientation.Horizontal)
                    currentRenderingTo = worksheetTo.Cells[firstRangeTo.Row, firstRangeTo.Column + Width];
                else
                    currentRenderingTo = worksheetTo.Cells[firstRangeTo.Row + Height, firstRangeTo.Column];
            }

            if (bindingContext.ElementsToRender != null && bindingContext.ElementsToRender.Count > 0)
            {
                if (templateDefinition.HasLinkedTemplates)
                    ManageTemplateWithLinkedTemplates();
                else
                    ManageTemplateWithoutLinkedTemplates();
            }

            //if (view.FooterView != null)
            //{
            //    ExcelHeaderFooterPartRenderer footer = new ExcelHeaderFooterPartRenderer(view.FooterView);
            //    footer.Render();
            //}

            if (Height > 0 && Width > 0)
                OutputRange = firstRangeTo.Resize[Height, Width];

            Marshal.ReleaseComObject(worksheetTo);
        }

        public void Dispose()
        {
            //Marshal.ReleaseComObject(firstRangeTo);
            //Marshal.ReleaseComObject(elementFirstRangeTo);
            //Marshal.ReleaseComObject(currentRenderingFrom);
            //Marshal.ReleaseComObject(currentRenderingTo);

            firstRangeTo = null;
            elementFirstRangeTo = null;
            currentRenderingFrom = null;
            currentRenderingTo = null;
        }
        #endregion

        #region private method
        private void Init()
        {
            Height = Width = 0;
            DataRows = new List<List<IBindingContextItem>>();
        }
        #endregion

        #region private methods
        private void ManageTemplateWithoutLinkedTemplates()
        { 
            int cpt = 0;
            Range workingRange;
            Range firstCell = currentRenderingTo;
            Worksheet worksheetTo = currentRenderingTo.Worksheet;

            if (templateDefinition.Orientation == Orientation.Horizontal)
            {
                int localWidth = templateDefinition.Width * bindingContext.ElementsToRender.Count;
                int localHeight = templateDefinition.Height;
                workingRange = currentRenderingTo.Resize[localHeight, localWidth];

                templateDefinition.TemplateCells.Copy(workingRange);
                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row + localHeight, currentRenderingTo.Column + localWidth];

                for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                    DataRows.Add(new List<IBindingContextItem>());

                for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
                {
                    cpt = 0;
                    for (int colId = 0; colId < templateDefinition.Width; colId++)
                    {
                        for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                        {
                            IBindingContextItem item = templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++];
                            if (item is IExcelControl)
                                ((IExcelControl)item).CreateControl(worksheetTo.Cells[firstCell.Row + rowId + elementCpt * templateDefinition.Height, firstCell.Column + colId]);
                            DataRows[rowId].Add(item);
                        }
                    }
                }
                Width += localWidth;
                if (Height < localHeight)
                    Height = localHeight;
            }
            else
            {
                int localWidth = templateDefinition.Width;
                int localHeight = templateDefinition.Height * bindingContext.ElementsToRender.Count;
                workingRange = currentRenderingTo.Resize[localHeight, localWidth];

                templateDefinition.TemplateCells.Copy(workingRange);
                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row + localHeight, currentRenderingTo.Column + localWidth];

                for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
                {
                    cpt = 0;
                    for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                    {
                        List<IBindingContextItem> row = new List<IBindingContextItem>();
                        DataRows.Add(row);
                        for (int colId = 0; colId < templateDefinition.Width; colId++)
                        {
                            IBindingContextItem item = templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++];
                            if (item is IExcelControl)
                                ((IExcelControl)item).CreateControl(worksheetTo.Cells[firstCell.Row + rowId + elementCpt * templateDefinition.Height, firstCell.Column + colId]);
                            row.Add(item);
                        }
                    }
                }

                Height += localHeight;
                if (Width < localWidth)
                    Width = localWidth;
            }

            Marshal.ReleaseComObject(worksheetTo);
            Marshal.ReleaseComObject(workingRange);
            firstCell = null;
        }

        private void ManageTemplateWithLinkedTemplates()
        {
            Worksheet worksheetTo = currentRenderingTo.Worksheet;
            for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
            {
                int bindingContextItemsCpt = 0;
                int cptLinkedDefinition = 0;
                int elementHeight = 0;
                int elementWidth = templateDefinition.Width;
                for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                {
                    List<IBindingContextItem> dataRow;
                    if (templateDefinition.Orientation == Orientation.Vertical || rowId >= DataRows.Count)
                    {
                        dataRow = new List<IBindingContextItem>();
                        DataRows.Add(dataRow);
                    }
                    else
                        dataRow = DataRows[rowId];

                    List<int> posLinks = templateDefinition.PositionLinkedTemplates[rowId];
                    if (posLinks == null)
                    {
                        elementHeight++;
                        if (templateDefinition.Orientation == Orientation.Horizontal && dataRow.Count < Width)
                            dataRow.AddRange(new IBindingContextItem[Width - dataRow.Count]);
                        ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, 0, templateDefinition.Width, 1, 0);
                    }
                    else
                    {
                        int refRow = templateDefinition.Orientation == Orientation.Horizontal ? rowId : DataRows.Count - 1;
                        int currentRowHeight = 1;
                        int currentRowWidth = 0;
                        int previousPos = 0;
                        int lastPosLink = posLinks.Count - 1;
                        for (int linkCpt = 0; linkCpt < posLinks.Count; linkCpt++)
                        {
                            int linkedViewRenderedWidth = 0;
                            // Render before link
                            int pos = posLinks[linkCpt];
                            if (pos > 0)
                            {
                                int gap = pos - previousPos;
                                if (gap > 0)
                                {
                                    ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, previousPos, pos, 0, gap);
                                    currentRowWidth += gap;
                                }
                            }
                            // Render link
                            LinkedTemplateDefinition linkedTemplateDefinition = templateDefinition.TemplatesParts[rowId, pos] as LinkedTemplateDefinition;
                            IBindingContext linkedBindingContext = (IBindingContext) bindingContext.ElementsToRender[elementCpt].LinkedBindingContexts[cptLinkedDefinition++];
                            if (linkedBindingContext.ElementsToRender == null || linkedBindingContext.ElementsToRender.Count == 0)
                            {
                                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row, currentRenderingTo.Column + 1];
                                dataRow.Add(null);
                                linkedViewRenderedWidth = 1;
                            }
                            else
                            {
                                using (ExcelPartRenderer linkedRenderer = new ExcelPartRenderer((ExcelTemplateDefinitionPart) linkedTemplateDefinition.TemplateDefinition, linkedBindingContext, this))
                                {
                                    linkedRenderer.Render();
                                    dataRow.AddRange(linkedRenderer.DataRows[0]);

                                    linkedViewRenderedWidth = linkedRenderer.Width;
                                    // Positioning = Absolute => Squeeze the concerned context items
                                    if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Absolute)
                                    {
                                        int upperBound = templateDefinition.TemplatesParts.GetUpperBound(1);
                                        for (int i = 1; i < linkedViewRenderedWidth; i++)
                                        {
                                            int cellId = i + pos;
                                            if (cellId > upperBound)
                                                break;
                                            if (templateDefinition.TemplatesParts[rowId, cellId] != null)
                                                bindingContextItemsCpt++;
                                        }
                                    }

                                    for (int i = 1; i < linkedRenderer.Height; i++)
                                    {
                                        List<IBindingContextItem> rowToUse;
                                        if (templateDefinition.Orientation == Orientation.Horizontal && i >= DataRows.Count - refRow
                                            || templateDefinition.Orientation == Orientation.Vertical && i >= currentRowHeight)
                                        {
                                            rowToUse = currentRowWidth > 0 ? new List<IBindingContextItem>(new IBindingContextItem[currentRowWidth]) : new List<IBindingContextItem>();
                                            if (templateDefinition.Orientation == Orientation.Horizontal)
                                                rowToUse.AddRange(new IBindingContextItem[Width]);// + currentRowWidth]);
                                            DataRows.Add(rowToUse);
                                        }
                                        else
                                        {
                                            rowToUse = DataRows[i + refRow];
                                            rowToUse.AddRange(new List<IBindingContextItem>(new IBindingContextItem[pos - previousPos]));
                                        }
                                        rowToUse.AddRange(linkedRenderer.DataRows[i]);
                                    }

                                    if (currentRowHeight < linkedRenderer.Height)
                                        currentRowHeight = linkedRenderer.Height;

                                    if (templateDefinition.Orientation == Orientation.Horizontal && (elementHeight - refRow) < currentRowHeight)
                                        elementHeight = currentRowHeight + refRow;

                                    currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row, currentRenderingTo.Column + linkedRenderer.Width];
                                }
                            }
                            currentRowWidth += linkedViewRenderedWidth;
                            previousPos = pos + linkedViewRenderedWidth;

                            // Render after link
                            if (linkCpt == lastPosLink && pos != templateDefinition.Width)
                            {
                                if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Absolute)
                                {
                                    int startPosition = pos + linkedViewRenderedWidth;
                                    if (startPosition < templateDefinition.Width)
                                    {
                                        ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, startPosition, templateDefinition.Width, 0, -1);
                                        currentRowWidth += templateDefinition.Width - startPosition;
                                    }
                                }
                                else
                                {
                                    int startPosition = pos + 1;
                                    if (startPosition < templateDefinition.Width)
                                    {
                                        int realEnd = templateDefinition.Width;
                                        for (int i = templateDefinition.Width - 1; i >= startPosition; i--)
                                        {
                                            if (bindingContext.ElementsToRender[elementCpt].BindingContextItems[i] != null)
                                                break;
                                            realEnd--;
                                        }
                                        if (realEnd > 0)
                                        {
                                            ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, startPosition, realEnd, 0, -1);
                                            currentRowWidth += templateDefinition.Width - startPosition;
                                        }
                                    }
                                }
                            }
                            if (elementWidth < currentRowWidth)
                                elementWidth = currentRowWidth; 
                        }

                        if (templateDefinition.Orientation == Orientation.Horizontal)
                        {
                            if (elementHeight > Height)
                                Height = elementHeight;
                            Width += elementWidth;
                            currentRenderingTo = worksheetTo.Cells[firstRangeTo.Row, firstRangeTo.Column + Width];
                        }
                        else
                        {
                            elementHeight += currentRowHeight;
                            if (Width < elementWidth)
                                Width = elementWidth;
                            Height += elementHeight;
                            currentRenderingTo = worksheetTo.Cells[firstRangeTo.Row + Height, firstRangeTo.Column];                
                        }
                    }
                }
            }
            Marshal.ReleaseComObject(worksheetTo);
            worksheetTo = null;
        }

//        private void ManageTemplateWithLinkedTemplates()
//        {
//            Worksheet worksheetTo = currentRenderingTo.Worksheet;
//            if (templateDefinition.Orientation == Orientation.Horizontal)
//                Height += templateDefinition.Height;

//            for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
//            {
//                int bindingContextItemsCpt = 0;
//                int cptLinkedDefinition = 0;
//                int elementWidth = templateDefinition.Width;

//                for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
//                {
//                    List<IBindingContextItem> dataRow;
//                    if (templateDefinition.Orientation == Orientation.Vertical || rowId >= DataRows.Count)
//                    {
//                        dataRow = new List<IBindingContextItem>();
//                        DataRows.Add(dataRow);
//                    }
//                    else
//                        dataRow = DataRows[rowId];

//                    List<int> posLinks = templateDefinition.PositionLinkedTemplates[rowId];
//                    if (posLinks == null)
//                    {
//                        Height++;
//                        if (templateDefinition.Orientation == Orientation.Horizontal && dataRow.Count < Width)
//                            dataRow.AddRange(new IBindingContextItem[Width - dataRow.Count]);
//                        ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, 0, templateDefinition.Width, 1, 0);
//                    }
//                    else
//                    {
////                        int elementHeight = 0;
//                        int refRow = templateDefinition.Orientation == Orientation.Horizontal ? rowId : DataRows.Count - 1;
//                        int currentElementHeight = 1;
//                        int currentElementWidth = 0;
//                        int previousPos = 0;
//                        int lastPosLink = posLinks.Count - 1;
//                        for (int linkCpt = 0; linkCpt < posLinks.Count; linkCpt++)
//                        {
//                            int linkedViewRenderedWidth = 0;
//                            // Render before link
//                            int pos = posLinks[linkCpt];
//                            if (pos > 0)
//                            {
//                                int gap = pos - previousPos;
//                                if (gap > 0)
//                                {
//                                    ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, previousPos, pos, 0, gap);
//                                    currentElementWidth += gap;
//                                }
//                            }
//                            // Render link
//                            LinkedTemplateDefinition linkedTemplateDefinition = templateDefinition.TemplatesParts[rowId, pos] as LinkedTemplateDefinition;
//                            IBindingContext linkedBindingContext = (IBindingContext)bindingContext.ElementsToRender[elementCpt].LinkedBindingContexts[cptLinkedDefinition++];
//                            if (linkedBindingContext.ElementsToRender == null || linkedBindingContext.ElementsToRender.Count == 0)
//                            {
//                                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row, currentRenderingTo.Column + 1];
//                                dataRow.Add(null);
//                                linkedViewRenderedWidth = 1;
//                            }
//                            else
//                            {
//                                using (ExcelPartRenderer linkedRenderer = new ExcelPartRenderer((ExcelTemplateDefinitionPart)linkedTemplateDefinition.TemplateDefinition, linkedBindingContext, this))
//                                {
//                                    linkedRenderer.Render();
//                                    dataRow.AddRange(linkedRenderer.DataRows[0]);

//                                    linkedViewRenderedWidth = linkedRenderer.Width;
//                                    // Positioning = Absolute => Squeeze the concerned context items
//                                    if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Absolute)
//                                    {
//                                        int upperBound = templateDefinition.TemplatesParts.GetUpperBound(1);
//                                        for (int i = 1; i < linkedViewRenderedWidth; i++)
//                                        {
//                                            int cellId = i + pos;
//                                            if (cellId > upperBound)
//                                                break;
//                                            if (templateDefinition.TemplatesParts[rowId, cellId] != null)
//                                                bindingContextItemsCpt++;
//                                        }
//                                    }

//                                    for (int i = 1; i < linkedRenderer.Height; i++)
//                                    {
//                                        List<IBindingContextItem> rowToUse;
//                                        if (templateDefinition.Orientation == Orientation.Horizontal && i >= DataRows.Count - refRow
//                                            || templateDefinition.Orientation == Orientation.Vertical && i >= currentElementHeight)
//                                        {
//                                            rowToUse = currentElementWidth > 0 ? new List<IBindingContextItem>(new IBindingContextItem[currentElementWidth]) : new List<IBindingContextItem>();
//                                            if (templateDefinition.Orientation == Orientation.Horizontal)
//                                                rowToUse.AddRange(new IBindingContextItem[Width]);// + currentRowWidth]);
//                                            DataRows.Add(rowToUse);
//                                        }
//                                        else
//                                        {
//                                            rowToUse = DataRows[i + refRow];
//                                            rowToUse.AddRange(new List<IBindingContextItem>(new IBindingContextItem[pos - previousPos]));
//                                        }
//                                        rowToUse.AddRange(linkedRenderer.DataRows[i]);
//                                    }

//                                    if (currentElementHeight < linkedRenderer.Height)
//                                        currentElementHeight = linkedRenderer.Height;

//                                    if (templateDefinition.Orientation == Orientation.Horizontal && Height > currentElementHeight)
//                                        currentElementHeight = Height;

//                                    currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row, currentRenderingTo.Column + linkedRenderer.Width];
//                                }
//                            }
//                            currentElementWidth += linkedViewRenderedWidth;
//                            previousPos = pos + linkedViewRenderedWidth;

//                            // Render after link
//                            if (linkCpt == lastPosLink && pos != templateDefinition.Width)
//                            {
//                                if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Absolute)
//                                {
//                                    int startPosition = pos + linkedViewRenderedWidth;
//                                    if (startPosition < templateDefinition.Width)
//                                    {
//                                        ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, startPosition, templateDefinition.Width, 0, -1);
//                                        currentElementWidth += templateDefinition.Width - startPosition;
//                                    }
//                                }
//                                else
//                                {
//                                    int startPosition = pos + 1;
//                                    if (startPosition < templateDefinition.Width)
//                                    {
//                                        int realEnd = templateDefinition.Width;
//                                        for (int i = templateDefinition.Width - 1; i >= startPosition; i--)
//                                        {
//                                            if (bindingContext.ElementsToRender[elementCpt].BindingContextItems[i] != null)
//                                                break;
//                                            realEnd--;
//                                        }
//                                        if (realEnd > 0)
//                                        {
//                                            ManageTemplatePart(dataRow, ref bindingContextItemsCpt, rowId, elementCpt, startPosition, realEnd, 0, -1);
//                                            currentElementWidth += templateDefinition.Width - startPosition;
//                                        }
//                                    }
//                                }
//                            }
//                            if (elementWidth < currentElementWidth)
//                                elementWidth = currentElementWidth;
//                        }

//                        if (templateDefinition.Orientation == Orientation.Horizontal)
//                        {
//                            if (currentElementHeight > templateDefinition.Height)
//                                Height = currentElementHeight;
//                            else
//                                Height += templateDefinition.Height;
//                            Width += elementWidth;
//                            currentRenderingTo = worksheetTo.Cells[firstRangeTo.Row, firstRangeTo.Column + Width];
//                        }
//                        else
//                        {
//                            if (elementWidth > Width)
//                                Width = elementWidth;
//                            Height += currentElementHeight;
//                            currentRenderingTo = worksheetTo.Cells[firstRangeTo.Row + Height, firstRangeTo.Column];
//                        }
//                    }
//                }
//            }
//            Marshal.ReleaseComObject(worksheetTo);
//            worksheetTo = null;
//        }

        private void ManageTemplatePart(List<IBindingContextItem> row, ref int cpt, int rowId, int elementCpt, int startPos, int endPos, int yOffset, int xOffset)
        {
            Worksheet worksheetFrom = templateDefinition.TemplateFirstCell.Worksheet;
            Worksheet worksheetTo = currentRenderingTo.Worksheet;

            for (int colId = startPos; colId < endPos; colId++)
            {
                IBindingContextItem item = templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++];
                if (item is IExcelControl)
                    ((IExcelControl)item).CreateControl(worksheetTo.Cells[currentRenderingTo.Row, currentRenderingTo.Column + colId]);
                row.Add(item);
            }

            int gap = endPos - startPos;
            Range source = worksheetFrom.Cells[templateDefinition.TemplateFirstCell.Row + rowId, templateDefinition.TemplateFirstCell.Column + startPos];
            source = source.Resize[1, gap];
            Range workingRange = currentRenderingTo.Resize[1, gap];
            source.Copy(workingRange);
            if (xOffset != -1)
                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row + yOffset, currentRenderingTo.Column + xOffset];

            Marshal.ReleaseComObject(worksheetFrom);
            Marshal.ReleaseComObject(worksheetTo);
            Marshal.ReleaseComObject(source);
            Marshal.ReleaseComObject(workingRange);
            worksheetFrom = null;
            worksheetTo = null;
            source = null;
            workingRange = null;
        }
        #endregion
    }
}
